/*
 *
 * Paros and its related class files.
 *
 * Paros is an HTTP/HTTPS proxy for assessing web application security.
 * Copyright (C) 2003-2004 Chinotec Technologies Company
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Clarified Artistic License
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Clarified Artistic License for more details.
 *
 * You should have received a copy of the Clarified Artistic License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
// ZAP: 2011/11/20 Set order and name
// ZAP: 2012/03/15 Added the method getProxyListenerOrder. Set the name of the
// extension filter thread.
// ZAP: 2012/03/17 Issue 282 Added getAuthor()
// ZAP: 2012/04/25 Added type argument to generic type, removed unnecessary
// casts and added @Override annotation to all appropriate methods.
// ZAP: 2012/06/25 Added addFilter() plus searchFilterIndex() method, that
// allows to add some custom filter to the FilterFactory (e.g.: by third
// party extensions).
// ZAP: 2012/08/01 Issue 332: added support for Modes
// ZAP: 2013/01/25 Added method removeFilter().
// ZAP: 2013/01/25 Removed the "(non-Javadoc)" comments.
// ZAP: 2013/03/03 Issue 546: Remove all template Javadoc comments
// ZAP: 2013/11/16 Issue 902 - Change all ExtensionAdaptor#hook(ExtensionHook) overriding methods
// to call the base implementation
// ZAP: 2014/01/28 Issue 207: Support keyboard shortcuts
// ZAP: 2014/11/26 Fixed an issue in the implementation of searchFilterIndex.
// ZAP: 2015/03/16 Issue 1525: Further database independence changes
// ZAP: 2016/06/28 Do not start the timer thread if no filter is enabled
// ZAP: 2017/04/07 Added getUIName()
// ZAP: 2017/12/28 Add deprecated annotation and JavaDoc tag.
// ZAP: 2019/06/01 Normalise line endings.
// ZAP: 2019/06/05 Normalise format/style.
package org.parosproxy.paros.extension.filter;

import java.util.List;
import org.apache.log4j.Logger;
import org.parosproxy.paros.Constant;
import org.parosproxy.paros.control.Control;
import org.parosproxy.paros.core.proxy.ProxyListener;
import org.parosproxy.paros.extension.ExtensionAdaptor;
import org.parosproxy.paros.extension.ExtensionHook;
import org.parosproxy.paros.extension.ViewDelegate;
import org.parosproxy.paros.model.Model;
import org.parosproxy.paros.network.HttpMessage;
import org.zaproxy.zap.view.ZapMenuItem;

/** @deprecated (2.8.0) Filters were superseded by scripts and Replacer add-on. */
@Deprecated
public class ExtensionFilter extends ExtensionAdaptor implements ProxyListener {

    private static final Logger log = Logger.getLogger(ExtensionFilter.class);

    public static final String NAME = "ExtensionFilter";
    public static final int PROXY_LISTENER_ORDER = 0;

    private ZapMenuItem menuToolsFilter = null;
    private FilterFactory filterFactory = new FilterFactory();
    private TimerFilterThread timerFilterThread;

    public ExtensionFilter() {
        super();
        this.setOrder(8);
    }

    @Override
    public boolean isDepreciated() {
        return true;
    }

    @Override
    public String getUIName() {
        return Constant.messages.getString("filter.name");
    }

    @Override
    public void init() {
        this.setName(NAME);
        filterFactory.loadAllFilter();
    }

    @Override
    public void initModel(Model model) {
        // ZAP: changed to init(Model)
        super.initModel(model);
        Filter filter = null;
        // ZAP: Added type argument.
        List<Filter> filters = filterFactory.getAllFilter();
        for (int i = 0; i < filters.size(); i++) {
            // ZAP: Removed unnecessary cast.
            filter = filters.get(i);
            try {
                filter.init(model);
            } catch (Exception ignore) {
                log.warn("Error initializing filter. Continuing.", ignore);
            }
        }
    }

    @Override
    public void initView(ViewDelegate view) {
        super.initView(view);
        Filter filter = null;
        for (int i = 0; i < filterFactory.getAllFilter().size(); i++) {
            // ZAP: Removed unnecessary cast.
            filter = filterFactory.getAllFilter().get(i);
            try {
                filter.initView(view);
            } catch (Exception ignore) {
                log.warn("Error initializing view for filter. Continuing.", ignore);
            }
        }
    }

    /**
     * This method initializes menuToolsFilter
     *
     * @return javax.swing.JMenuItem
     */
    private ZapMenuItem getMenuToolsFilter() {
        if (menuToolsFilter == null) {
            menuToolsFilter = new ZapMenuItem("menu.tools.filter");
            menuToolsFilter.addActionListener(
                    new java.awt.event.ActionListener() {

                        @Override
                        public void actionPerformed(java.awt.event.ActionEvent e) {

                            FilterDialog dialog = new FilterDialog(getView().getMainFrame());
                            dialog.setAllFilters(filterFactory.getAllFilter());
                            dialog.showDialog(false);

                            boolean startThread = false;
                            for (Filter filter : filterFactory.getAllFilter()) {
                                if (filter.isEnabled()) {
                                    startThread = true;
                                    break;
                                }
                            }

                            if (startThread) {
                                if (timerFilterThread == null) {
                                    timerFilterThread =
                                            new TimerFilterThread(filterFactory.getAllFilter());
                                    timerFilterThread.start();
                                }
                            } else if (timerFilterThread != null) {
                                timerFilterThread.setStopped();
                                timerFilterThread = null;
                            }
                        }
                    });
        }
        return menuToolsFilter;
    }

    @Override
    public void hook(ExtensionHook extensionHook) {
        super.hook(extensionHook);
        if (getView() != null) {
            extensionHook.getHookMenu().addToolsMenuItem(getMenuToolsFilter());
        }
        extensionHook.addProxyListener(this);
    }

    // ZAP: Added the method.
    @Override
    public int getArrangeableListenerOrder() {
        return PROXY_LISTENER_ORDER;
    }

    @Override
    public boolean onHttpRequestSend(HttpMessage httpMessage) {
        Filter filter = null;
        // Check mode
        switch (Control.getSingleton().getMode()) {
            case safe:
                // Only safe thing to do is to disable all filters
                return true;
            case protect:
                if (!httpMessage.isInScope()) {
                    // Target not in scope, so ignore
                    return true;
                }
        }
        for (int i = 0; i < filterFactory.getAllFilter().size(); i++) {
            // ZAP: Removed unnecessary cast.
            filter = filterFactory.getAllFilter().get(i);
            try {
                if (filter.isEnabled()) {
                    filter.onHttpRequestSend(httpMessage);
                }
            } catch (Exception e) {
                // ZAP: Changed to log the exception.
                log.error(e.getMessage(), e);
            }
        }
        return true;
    }

    @Override
    public boolean onHttpResponseReceive(HttpMessage httpMessage) {
        Filter filter = null;
        // Check mode
        switch (Control.getSingleton().getMode()) {
            case safe:
                return true;
            case protect:
                if (!httpMessage.isInScope()) {
                    return true;
                }
        }
        for (int i = 0; i < filterFactory.getAllFilter().size(); i++) {
            // ZAP: Removed unnecessary cast.
            filter = filterFactory.getAllFilter().get(i);
            try {
                if (filter.isEnabled()) {

                    filter.onHttpResponseReceive(httpMessage);
                }
            } catch (Exception e) {
                // ZAP: Changed to log the exception.
                log.error(e.getMessage(), e);
            }
        }
        return true;
    }

    /** Destroy every filter during extension destroy. */
    @Override
    public void destroy() {
        if (timerFilterThread != null) {
            timerFilterThread.setStopped();
            timerFilterThread = null;
        }

        Filter filter = null;
        for (int i = 0; i < filterFactory.getAllFilter().size(); i++) {
            // ZAP: Removed unnecessary cast.
            filter = filterFactory.getAllFilter().get(i);
            try {
                filter.destroy();
            } catch (Exception e) {
            }
        }
    }

    @Override
    public String getAuthor() {
        return Constant.PAROS_TEAM;
    }

    /**
     * Allows to add a filter. The {@link Filter#getId()} method is used to determine its position
     * in the list, as a TreeMap is used.
     *
     * @param filter
     */
    // ZAP: Added the method.
    public void addFilter(Filter filter) {
        List<Filter> filters = filterFactory.getAllFilter();

        int index = searchFilterIndex(filters, filter.getId(), 0, filters.size());

        if (index == -1) {
            // not found - put at the end
            filters.add(filter);
        } else {
            filters.add(index, filter);
        }
    }

    public void removeFilter(Filter filter) {
        List<Filter> filters = filterFactory.getAllFilter();
        filters.remove(filter);
    }

    /**
     * Does a binary search for the given filter id. Used to determine where (index) to insert the
     * filter to the filter's list.
     *
     * @param filters
     * @param targetId
     * @param min
     * @param max
     * @return
     */
    // ZAP: Added the method.
    private int searchFilterIndex(List<Filter> filters, int targetId, int min, int max) {
        // Basic algorithm taken from Wikipedia:
        // http://en.wikipedia.org/wiki/Binary_search_algorithm#Recursive
        if (max <= min) {
            // set is empty, so return value showing not found
            return -1;
        }

        // calculate midpoint to cut set in half
        int mid = (min + max) / 2;

        // three-way comparison
        int id = filters.get(mid).getId();
        if (id > targetId) {
            // id is in lower subset
            return searchFilterIndex(filters, targetId, min, mid - 1);
        } else if (id < targetId) {
            // id is in upper subset
            return searchFilterIndex(filters, targetId, mid + 1, max);
        }

        // index has been found
        return mid + 1;
    }

    /** No database tables used, so all supported */
    @Override
    public boolean supportsDb(String type) {
        return true;
    }

    private static class TimerFilterThread extends Thread {

        private final List<Filter> filters;
        private boolean stop;

        public TimerFilterThread(List<Filter> filters) {
            super("ZAP-ExtensionFilter");
            setDaemon(true);

            this.filters = filters;
        }

        @Override
        public void run() {
            while (!stop) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e1) {
                }

                for (Filter filter : filters) {
                    try {
                        if (filter.isEnabled()) {
                            filter.timer();
                        }
                    } catch (Exception e) {
                    }
                }
            }
        }

        public void setStopped() {
            this.stop = true;
        }
    }
}
